package br.com.ac.fatureweb.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.FlushModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import br.com.ac.fatureweb.enums.EnumStatusCarga;
import br.com.ac.fatureweb.enums.Status;
import br.com.ac.fatureweb.exception.NgcException;
import br.com.ac.fatureweb.integration.billing.FotografiaVO;
import br.com.ac.fatureweb.integration.model.RoteiroVO;
import br.com.ac.fatureweb.model.Carga;
import br.com.ac.fatureweb.model.Equipamento;
import br.com.ac.fatureweb.model.Fotografia;
import br.com.ac.fatureweb.model.ItemRoteiro;
import br.com.ac.fatureweb.model.Roteiro;
import br.com.ac.fatureweb.model.billing.LogLeitura;
import br.com.ac.fatureweb.model.carga.Imovel;
import br.com.ac.fatureweb.utilitary.FacesUtils;
import br.com.ac.fatureweb.utilitary.NgcDefaultBean;

@Local
@Named
@Stateless
public class NgcRoteiroBean extends NgcDefaultBean<Roteiro> {

	@PersistenceContext
	private EntityManager entityManager;

	@PostConstruct
	void inicialize() {
		setEm(entityManager);
	}

	@SuppressWarnings("unchecked")
	public List<Carga> getLstCarga(EnumStatusCarga status) {

		Query query = entityManager.createQuery("SELECT c FROM Carga c"
				+ " WHERE c.enumStatusCarga =:enumStatusCarga");

		query.setParameter("enumStatusCarga", status);

		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public void gerarRoteiro(Long id, Integer qtdLeiturista,
			Date dataFaturamento) {

		entityManager.setFlushMode(FlushModeType.COMMIT);

		Query query = entityManager.createQuery("SELECT i FROM Imovel i "
				+ " INNER JOIN i.carga car "
				+ " WHERE car.id =:id ORDER BY i.ordemRoteiro");

		query.setParameter("id", id);

		List<Imovel> lstImovel = query.getResultList();

		if (lstImovel != null && !lstImovel.isEmpty()) {

			// TODO Gerar o roteiro e associar os imóveis.
			Integer qtdImovelRoteiro = lstImovel.size() / qtdLeiturista;

			Integer qtdRoteiro = lstImovel.size() / qtdImovelRoteiro;

			int posicaoInicial = 0;

			for (int index = 1; index <= qtdRoteiro; index++) {

				posicaoInicial = gerarRoteiro(id, lstImovel, qtdImovelRoteiro,
						qtdRoteiro, posicaoInicial, index, dataFaturamento);
			}
		}

		Carga carga = entityManager.getReference(Carga.class, id);

		carga.setEnumStatusCarga(EnumStatusCarga.GERADO);

		entityManager.merge(carga);
	}

	/**
	 * 
	 * @param id
	 * @param lstImovel
	 * @param qtdImovelRoteiro
	 * @param qtdRoteiro
	 * @param posicaoInicial
	 * @param index
	 * @param dataFaturamento
	 * @return
	 */
	private int gerarRoteiro(Long id, List<Imovel> lstImovel,
			Integer qtdImovelRoteiro, Integer qtdRoteiro, int posicaoInicial,
			int index, Date dataFaturamento) {
		Roteiro roteiro = new Roteiro();

		roteiro.setCarga(new Carga(id));

		roteiro.setNumero(index);

		String mesS = lstImovel.iterator().next().getMes().toString();

		if (lstImovel.iterator().next().getMes() < 10) {
			mesS = "0" + lstImovel.iterator().next().getMes();
		}

		roteiro.setCodSetor(lstImovel.iterator().next().getCodSetor());

		roteiro.setCompetencia(new Long(""
				+ lstImovel.iterator().next().getAno() + mesS));

		roteiro.setDataGeracao(new Date());

		roteiro.setDataFaturamento(dataFaturamento);

		roteiro.setStatus(Status.DISPONIVEL);

		roteiro.setLstItemRoteiro(new ArrayList<ItemRoteiro>());

		int posicaoFinal = posicaoInicial + qtdImovelRoteiro;

		if (index == qtdRoteiro) {
			posicaoFinal = lstImovel.size();
		}

		List<Imovel> subListImovel = gerarItemRoteiro(lstImovel,
				posicaoInicial, roteiro, posicaoFinal);

		roteiro.setQtdImovel(subListImovel.size());

		entityManager.persist(roteiro);

		posicaoInicial += qtdImovelRoteiro;
		return posicaoInicial;
	}

	/**
	 * 
	 * @param lstImovel
	 * @param posicaoInicial
	 * @param roteiro
	 * @param posicaoFinal
	 * @return
	 */
	private List<Imovel> gerarItemRoteiro(List<Imovel> lstImovel,
			int posicaoInicial, Roteiro roteiro, int posicaoFinal) {
		List<Imovel> subListImovel = new ArrayList<Imovel>(lstImovel.subList(
				posicaoInicial, posicaoFinal));

		int contador = 1;

		for (Imovel imovel : subListImovel) {

			ItemRoteiro itemRoteiro = new ItemRoteiro();

			itemRoteiro.setOrdem(contador);

			itemRoteiro.setImovel(imovel);

			itemRoteiro.setRoteiro(roteiro);

			itemRoteiro.setCarga(roteiro.getCarga());

			roteiro.getLstItemRoteiro().add(itemRoteiro);

			contador++;
		}
		return subListImovel;
	}

	/**
	 * 
	 * Desassociar itens de um roteiro.
	 * 
	 * @param entity
	 */
	public void desassociarItensRoteiro(Set<ItemRoteiro> list) {
		ItemRoteiro itemRoteiro = null;

		if (list != null) {
			for (ItemRoteiro item : list) {
				itemRoteiro = entityManager.getReference(ItemRoteiro.class,
						item.getId());
				itemRoteiro.setRoteiro(null);

				entityManager.merge(itemRoteiro);
			}
		}
	}

	/**
	 * 
	 * Associar itens a um roteiro.
	 * 
	 * @param list
	 * @param entity
	 */
	public void associarItensRoteiro(Set<ItemRoteiro> list, Roteiro roteiro) {
		ItemRoteiro orfao = null;

		if (list != null) {
			for (ItemRoteiro item : list) {
				orfao = entityManager.getReference(ItemRoteiro.class,
						item.getId());
				orfao.setRoteiro(roteiro);

				entityManager.merge(orfao);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public List<Equipamento> getLstEquipamento() {
		Query query = entityManager.createQuery("FROM Equipamento "
				+ " WHERE dataInativacao IS NULL");

		return query.getResultList();
	}

	/**
	 * 
	 * Retorna a lista de itens de um determinado roteiro.
	 * 
	 * @param idRoteiro
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<ItemRoteiro> getLstItensRoteiro(Long idRoteiro) {
		Query query = entityManager.createQuery("FROM ItemRoteiro "
				+ " WHERE roteiro.id = :idRoteiro " + " ORDER BY ordem ASC");

		query.setParameter("idRoteiro", idRoteiro);

		return query.getResultList();
	}

	/**
	 * 
	 * Recuperar todos os itens que não estejam associados a um roteiro.
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<ItemRoteiro> buscarItensOrfaos(Long idCarga) {
		List<ItemRoteiro> result = null;

		Query query = entityManager.createQuery("FROM ItemRoteiro "
				+ " WHERE roteiro IS NULL " + " AND carga.id = :idCarga ");

		query.setParameter("idCarga", idCarga);

		result = query.getResultList();

		if (result != null && !result.isEmpty()) {
			for (ItemRoteiro itemRoteiro : result) {
				itemRoteiro.getImovel().getId();
			}
		}

		return result;
	}

	/**
	 * 
	 * Reordenar itens de um determinado roteiro.
	 * 
	 * @param idRoteiro
	 * @return
	 */
	public List<ItemRoteiro> reordenarItensRoteiro(Long idRoteiro) {
		List<ItemRoteiro> itens = getLstItensRoteiro(idRoteiro);
		List<ItemRoteiro> retorno = null;

		if (itens != null && !itens.isEmpty()) {

			retorno = new ArrayList<ItemRoteiro>();

			int ordem = 1;
			for (ItemRoteiro item : itens) {
				item.setOrdem(ordem);
				ordem++;

				ItemRoteiro itemOrdenado = entityManager.merge(item);
				itemOrdenado.getImovel().getId();
				retorno.add(itemOrdenado);
			}

			/*
			 * Atualizando a quantidade de imóveis do roteiro.
			 */
			Roteiro roteiro = entityManager.getReference(Roteiro.class,
					idRoteiro);
			roteiro.setQtdImovel(itens.size());
			entityManager.merge(roteiro);
		}

		return retorno;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see br.com.ac.fatureweb.utilitary.NgcDefaultBean#merge(java.lang.Object)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public Roteiro merge(Roteiro entity) {

		Query query = entityManager
				.createQuery("SELECT COALESCE(rot.ordem, 0) FROM Roteiro rot "
						+ " INNER JOIN rot.equipamento eq "
						+ " WHERE eq.imei =:imei ORDER BY rot.ordem DESC ");

		query.setParameter("imei", entity.getEquipamento().getImei());

		query.setMaxResults(1);

		List<Integer> lstOrdem = query.getResultList();

		entity.setOrdem(1);

		if (lstOrdem != null && !lstOrdem.isEmpty()) {
			entity.setOrdem(lstOrdem.iterator().next() + 1);
		}

		return super.merge(entity);
	}

	public Roteiro getRoteiro(Long id) {
		Roteiro roteiro = entityManager.find(Roteiro.class, id);

		roteiro.getLstItemRoteiro().size();
		roteiro.getCarga().getId();

		if (roteiro.getLstItemRoteiro() != null
				&& !roteiro.getLstItemRoteiro().isEmpty()) {
			for (ItemRoteiro itemRoteiro : roteiro.getLstItemRoteiro()) {
				itemRoteiro.getImovel().getId();
			}
		}

		Collections.sort(roteiro.getLstItemRoteiro(), (item1, item2) -> item1
				.getOrdem().compareTo(item2.getOrdem()));

		return roteiro;
	}

	/**
	 * Buscar o roteiro com a menor ordem para execução das leituras. Procurar
	 * os roteiros que estão com status disponível.
	 * 
	 * @param imei
	 *            Identificador do equipamento.
	 * 
	 * @return Roteiro com a menor ordem encontrado para carga.
	 */
	@SuppressWarnings("unchecked")
	public RoteiroVO findRoteiro(String imei) {
		Query query = entityManager.createQuery("SELECT rot "
				+ " FROM Roteiro rot INNER JOIN rot.equipamento eq "
				+ " WHERE eq.imei =:imei AND rot.status =:status"
				+ " ORDER BY rot.ordem ASC ");

		query.setParameter("imei", imei);

		query.setParameter("status", Status.DISPONIVEL);

		query.setMaxResults(1);

		List<Roteiro> lstRoteiro = query.getResultList();

		if (lstRoteiro != null && !lstRoteiro.isEmpty()) {
			Roteiro roteiro = lstRoteiro.iterator().next();

			Calendar calendar = Calendar.getInstance(new Locale("pt", "BR"));

			calendar.setTime(roteiro.getDataGeracao());

			RoteiroVO roteiroVO = new RoteiroVO();

			roteiroVO.setId(roteiro.getId());
			roteiroVO.setNumero(roteiro.getNumero());
			roteiroVO.setOrdem(roteiro.getOrdem());
			roteiroVO.setCompetencia(roteiro.getCompetencia());
			roteiroVO.setDataGeracao(calendar.getTimeInMillis());
			roteiroVO.setQtdImovel(roteiro.getQtdImovel());

			calendar.setTime(roteiro.getDataFaturamento());
			roteiroVO.setDataFaturamento(calendar.getTimeInMillis());

			return roteiroVO;
		}

		return null;
	}

	public void marcarRoteiroEmCampo(Long idRoteiro) throws NgcException {

		if (idRoteiro == null) {
			throw new NgcException("Id do roteiro não foi informado");
		}

		Roteiro roteiro = entityManager.getReference(Roteiro.class, idRoteiro);

		roteiro.setStatus(Status.CAMPO);

		roteiro.setDataCarga(new Date());

		entityManager.merge(roteiro);
	}

	public void marcarRoteiroRetornado(Long idRoteiro) throws NgcException {

		if (idRoteiro == null) {
			throw new NgcException("Id do roteiro não foi informado");
		}

		Roteiro roteiro = entityManager.getReference(Roteiro.class, idRoteiro);

		roteiro.setStatus(Status.RETORNADO);

		roteiro.setDataRetorno(new Date());

		entityManager.merge(roteiro);
	}

	@SuppressWarnings("unchecked")
	public List<Long> validateReturnRoute(List<Long> lstRetorno) {
		Query query = entityManager
				.createQuery("SELECT log.inscricao "
						+ " FROM LogLeitura log "
						+ " WHERE NOT EXISTS ( SELECT 1 FROM LogLeitura log1 WHERE log.inscricao = log1.inscricao  ) "
						+ " AND log.inscricao IN ( :param ) ");

		query.setParameter("param", lstRetorno);

		return query.getResultList();
	}

	@SuppressWarnings("unchecked")
	public List<Long> validateLogCorrectionRoute(List<Long> lstRetorno) {

		Query query = entityManager
				.createQuery("SELECT log.inscricao "
						+ " FROM LogCorrecaoConta log "
						+ " WHERE NOT EXISTS ( SELECT 1 FROM LogCorrecaoConta log1 WHERE log.inscricao = log1.inscricao  ) "
						+ " AND log.inscricao IN ( :param ) ");

		query.setParameter("param", lstRetorno);

		List<Long> resultList = query.getResultList();

		if (resultList == null || resultList.isEmpty()) {
			return lstRetorno;
		}

		return resultList;
	}

	/**
	 * 
	 * Retorna a quantidade de itens já associados a algum roteiro da carga.
	 * 
	 * @param idCarga
	 * @return
	 */
	public Long countItensEmRoteiro(Long idCarga) {
		Query query = entityManager
				.createQuery(" SELECT COUNT(item.id) FROM ItemRoteiro item "
						+ " INNER JOIN item.roteiro rot"
						+ " WHERE rot.carga.id = :idCarga ");

		query.setParameter("idCarga", idCarga);

		return (Long) query.getSingleResult();
	}

	/**
	 * 
	 * Retorna a quantidade de itens não associados a nenhum roteiro.
	 * 
	 * @return
	 */
	public Long countItensOrfaos(Long idCarga) {
		Query query = entityManager
				.createQuery(" SELECT COUNT(item.id) FROM ItemRoteiro item "
						+ " WHERE item.roteiro IS NULL "
						+ " AND item.carga.id = :idCarga ");

		query.setParameter("idCarga", idCarga);

		return (Long) query.getSingleResult();
	}

	/**
	 * Responsável por persistir as entidades que representam as fotografias do
	 * retorno.
	 * 
	 * @param lstFotografias
	 */
	public void receivePhotos(Long idRoteiro, List<FotografiaVO> lstFotografias) {
		// comitar tudo de uma vez só...
		entityManager.setFlushMode(FlushModeType.COMMIT);

		if (lstFotografias != null && !lstFotografias.isEmpty()) {
			for (FotografiaVO fotografiaVO : lstFotografias) {
				Fotografia foto = new Fotografia(fotografiaVO, idRoteiro);
				entityManager.persist(foto);
			}
		}
	}

	/**
	 * 
	 * Responsável por salvar as fotos e os backups do retorno em um diretório
	 * "ifature" localizado na raiz da máquina.
	 * 
	 * @param file
	 * @param fileName
	 * @param dir
	 * @throws IOException
	 */
	public void uploadFileByteArray(final byte[] file, final String fileName,
			final String dir, final boolean isSmb) throws IOException {

		FacesUtils.writeVfsFile(dir, fileName, file, isSmb);
	}

	@Override
	public void remove(Long id) throws NgcException {

		Roteiro bean = entityManager.getReference(Roteiro.class, id);

		Carga carga = bean.getCarga();

		entityManager.remove(bean);

		entityManager.flush();

		if (carga != null && carga.getId() != null) {
			// Se for o último roteiro da carga, deixar a carga disponível.
			TypedQuery<Roteiro> query = entityManager.createQuery(
					"SELECT rot FROM Roteiro rot INNER JOIN rot.carga carga "
							+ " WHERE carga.id =:id", Roteiro.class);

			query.setParameter("id", carga.getId());

			query.setMaxResults(1);

			List<Roteiro> resultList = query.getResultList();

			if (resultList != null && resultList.isEmpty()) {

				Carga reference = entityManager.getReference(Carga.class,
						carga.getId());

				reference.setEnumStatusCarga(EnumStatusCarga.DISPONIVEL);

				entityManager.merge(reference);

				entityManager
						.createNativeQuery(
								"DELETE FROM ITEM_ROTEIRO WHERE ID_CARGA=:ID")
						.setParameter("ID", carga.getId()).executeUpdate();

			}
		}
	}

	@Override
	public void remove(Roteiro entity) throws NgcException {
		super.remove(entity.getId());
	}

	@SuppressWarnings("unchecked")
	public List<Carga> getLstCargaRetorno() {
		Query query = entityManager.createQuery("SELECT c FROM Carga c"
				+ " WHERE c.enumStatusCarga <>:enumStatusCarga");

		query.setParameter("enumStatusCarga", EnumStatusCarga.ENVIADO_SAAE);

		return query.getResultList();
	}

	public void marcarRoteiroSaae(Long idRoteiro) throws NgcException {
		if (idRoteiro == null) {
			throw new NgcException("Id do roteiro não foi informado");
		}

		Roteiro roteiro = entityManager.getReference(Roteiro.class, idRoteiro);

		roteiro.setStatus(Status.CAMPO);

		roteiro.setDataCarga(new Date());

		entityManager.merge(roteiro);
	}

	public void atualizarCarga(Carga carga) {
		Carga cargaM = entityManager.getReference(Carga.class, carga.getId());

		cargaM.setDataRetorno(new Date());

		cargaM.setEnumStatusCarga(EnumStatusCarga.ENVIADO_SAAE);

		entityManager.merge(cargaM);
	}

	public void marcarLogLeituraEnviado(Long id) {
		LogLeitura reference = entityManager.getReference(LogLeitura.class, id);

		reference.setEnumStatus(EnumStatusCarga.ENVIADO_SAAE);

		entityManager.merge(reference);
	}

	// @Override
	// public List<Roteiro> search(Map<String, Object> mapParam, int first,
	// int qtdSearch, Map<String, Order> mapOrder) {
	//
	// Map<String, Object> mapWhere = new HashMap<String, Object>();
	//
	// StringBuilder jpql = gerarWhere(mapParam, mapOrder, mapWhere, false);
	//
	// TypedQuery<Roteiro> query = entityManager.createQuery(jpql.toString(),
	// Roteiro.class);
	//
	// setParameterWhere(mapWhere, query);
	//
	// query.setFirstResult(first);
	//
	// query.setMaxResults(qtdSearch);
	//
	// List<Roteiro> resultList = query.getResultList();
	//
	// return resultList;
	// }
	//
	// private StringBuilder gerarWhere(Map<String, Object> mapParam,
	// Map<String, Order> mapOrder, Map<String, Object> mapWhere,
	// boolean isCount) {
	// StringBuilder jpql = new StringBuilder();
	//
	// if (isCount) {
	// jpql.append("SELECT COUNT(rot) ");
	// } else {
	// jpql.append("SELECT NEW Roteiro(rot.id, rot.numero, rot.competencia, "
	// +
	// " rot.status, rot.qtdImovel, rot.dataFaturamento, rot.dataCarga, rot.dataRetorno, "
	// + " leit.nome, rot.codSetor ) ");
	// }
	//
	// jpql.append(" FROM Roteiro rot LEFT JOIN rot.equipamento eq2 LEFT JOIN eq2.leiturista leit ");
	//
	// if (mapOrder != null && !mapParam.isEmpty()) {
	// jpql.append(" WHERE ");
	//
	// if (mapParam.containsKey("codSetor")) {
	// jpql.append(" rot.codSetor =:codSetor AND ");
	// mapWhere.put("codSetor", mapParam.get("codSetor"));
	// }
	//
	// if (mapParam.containsKey("competencia")) {
	// jpql.append(" rot.competencia =:competencia AND ");
	// mapWhere.put("competencia", mapParam.get("competencia"));
	// }
	//
	// if (mapParam.containsKey("dataFaturamento")) {
	// jpql.append(" date_trunc('day',rot.dataFaturamento) =:dataFaturamento AND ");
	// mapWhere.put("dataFaturamento", mapParam.get("dataFaturamento"));
	// }
	//
	// if (mapParam.containsKey("status")) {
	// jpql.append(" rot.status =:status AND ");
	// mapWhere.put("status", mapParam.get("status"));
	// }
	//
	// NgcDefaultBean.removeLastWord(jpql, "AND");
	// }
	//
	// if (!isCount) {
	// jpql.append(" ORDER BY rot.competencia DESC, rot.codSetor, rot.numero ");
	// }
	//
	// return jpql;
	// }
	//
	// @Override
	// public Integer count(Map<String, Object> mapParam,
	// Map<String, Order> mapOrder) {
	//
	// Map<String, Object> mapWhere = new HashMap<String, Object>();
	//
	// StringBuilder jpql = gerarWhere(mapParam, mapOrder, mapWhere, true);
	//
	// TypedQuery<Number> query = entityManager.createQuery(jpql.toString(),
	// Number.class);
	//
	// setParameterWhere(mapWhere, query);
	//
	// query.setFirstResult(0);
	//
	// query.setMaxResults(1);
	//
	// final List<Number> resultList = query.getResultList();
	//
	// return resultList != null && !resultList.isEmpty() ? resultList
	// .iterator().next().intValue() : 0;
	// }
}
